home *** CD-ROM | disk | FTP | other *** search
/ SGI Developer Toolbox 6.1 / SGI Developer Toolbox 6.1 - Disc 4.iso / public / plan / src / daemon.c < prev    next >
C/C++ Source or Header  |  1994-08-01  |  17KB  |  662 lines

  1. /*
  2.  * separate daemon program that reads the database and waits for events to
  3.  * trigger, and pops up menus or whatever the user specified. When a cycling
  4.  * event triggers, it is advanced in-core, but nothing is ever written back
  5.  * to the database file. The daemon re-reads the database whenever it
  6.  * receives a SIGHUP signal, which is sent by the main program after it
  7.  * writes the database. The daemon writes its pid to /tmp/.pland<UID>, or
  8.  * terminates if the file already exists. The purpose of this lockfile is
  9.  * to prevent multiple daemons, and to tell the plan program who to send
  10.  * the SIGHUP to.
  11.  *
  12.  * This program uses the $PATH environment variable to find programs.
  13.  *
  14.  * Author: thomas@bitrot.in-berlin.de (Thomas Driemeyer)
  15.  */
  16.  
  17. #ifndef MIPS
  18. #include <unistd.h>
  19. #include <stdlib.h>
  20. #endif
  21. #include <stdio.h>
  22. #ifndef VARARGS
  23. #include <stdarg.h>
  24. #endif
  25. #include <sys/types.h>
  26. #include <sys/wait.h>
  27. #include <fcntl.h>
  28. #include <signal.h>
  29. #include <time.h>
  30. #include <Xm/Xm.h>
  31. #include "cal.h"
  32. #ifndef RABBITS
  33. #include <pwd.h>
  34. #include <utmp.h>
  35. #ifdef AIXV3
  36. struct utmp *getutent();
  37. #endif
  38. #endif
  39. #ifndef NOLIMITS
  40. #include <limits.h>
  41. #endif
  42.  
  43. #ifndef SIGCHLD            /* for BSD systems */
  44. #define SIGCHLD SIGCLD
  45. #endif
  46. #ifndef PIPE_MAX        /* writing this many bytes never blocks */
  47. #define PIPE_MAX 512
  48. #endif
  49. #if defined(BSD)||defined(MIPS)    /* detach forked process from terminal */
  50. #define ORPHANIZE setpgrp(0,0)    /* session, and attach to pid 1 (init). */
  51. #else                /* This means we'll get no ^C and other */
  52. #define ORPHANIZE setsid()    /* signals. If -DBSD doesn't help, the */
  53. #endif                /* daemon works without, sort of. */
  54. #if defined(SUN)||defined(BSD)||defined(SVR4) /* use itimer instead of sleep */
  55. #define ITIMER            /* systems, because not only may sleep be */
  56. #endif                /* uninterruptible, it may lose SIGHUP. */
  57. #ifdef ITIMER
  58. #include <sys/time.h>
  59. #endif
  60. #ifdef MIPS
  61. extern struct passwd *getpwuid();
  62. extern struct utmp *getutent();
  63. #endif
  64.  
  65. /* #define    DEBUG        /* define this to get execution reports */
  66.  
  67. static popup_window(), send_mail(), write_script(), exec_program();
  68. static char *get_subject(), *get_icontitle();
  69. extern struct tm *time_to_tm();
  70. extern void set_tzone();
  71. extern time_t get_time();
  72. extern BOOL find_file(), startup_lock();
  73. #ifdef MIPS
  74. extern char *getenv(), *malloc(), *realloc();
  75. #endif
  76.  
  77. char            *progname;    /* argv[0] */
  78. extern char        lockpath[];    /* lockfile path (from lock.c) */
  79. struct config        config;        /* global configuration data */
  80. struct list        *mainlist;    /* list of all schedule entries */
  81. extern time_t        cutoff;        /* all triggers before this are done */
  82. static BOOL        reread;        /* caught SIGHUP, re-read mainlist */
  83. extern int        errno;        /* system error */
  84. static void sighand(), alert();
  85. #ifndef VARARGS
  86. void fatal(char *fmt, ...);
  87. #endif
  88.  
  89.  
  90. main(argc, argv)
  91.     char            **argv;
  92.     int            argc;
  93. {
  94.     PID_T            pid;        /* pid in lockfile */
  95.     int            reason;        /* what triggers next? */
  96.     time_t            now, twait;    /* now and wait for trigger */
  97.     struct entry        *entry;        /* next entry that triggers */
  98.     int            opt_k, opt_K;    /* TRUE if -k or -K, resp. */
  99. #ifdef ITIMER
  100.      struct itimerval    itv;        /* for sleep() replacement */
  101. #endif
  102.  
  103. #ifndef RABBITS
  104.     struct utmp        *u;        /* for scanning /etc/utmp */
  105.     struct passwd        *pw;        /* user's password entry */
  106.  
  107.     if (!(pw = getpwuid(getuid()))) {
  108.         fprintf(stderr, "%s: WARNING: can't read user name.\n%s\n", *argv,
  109.         "If you are running YP/NIS, you may have to recompile with -lsun");
  110.     }
  111. #endif
  112.     progname = argv[0];
  113. #ifndef DEBUG
  114.     (void)umask(0077);
  115.     setuid(getuid());
  116.     setgid(getgid());
  117.     if ((pid = fork()) > 0) {
  118.         sleep(1);
  119.         exit(0);
  120.     }
  121.     if (pid == -1)
  122.         fprintf(stderr, "%s: Warning: cannot fork\n", progname);
  123.     else
  124.         ORPHANIZE;
  125. #endif
  126.     signal(SIGHUP,  sighand);
  127.     signal(SIGINT,  sighand);
  128.     signal(SIGQUIT, sighand);        /* comment out any of */
  129.     signal(SIGILL,  sighand);        /* these if undefined */
  130.     signal(SIGBUS,  sighand);        /* */
  131.     signal(SIGSEGV, sighand);        /* */
  132.     signal(SIGTERM, sighand);        /* */
  133. #ifdef ITIMER
  134.     signal(SIGALRM, sighand);
  135. #endif
  136.  
  137.     opt_k = argc > 1 && argv[1][0] == '-' && argv[1][1] == 'k';
  138.     opt_K = argc > 1 && argv[1][0] == '-' && argv[1][1] == 'K';
  139.     if (!startup_lock(LOCK_PATH, opt_k || opt_K)) {
  140.         if (opt_k || opt_K)
  141.             fatal("cannot kill existing %s daemon", progname);
  142.         else
  143.             fatal("another daemon exists, use %s -k", progname);
  144.     }
  145.     if (opt_K)
  146.         exit(0);
  147.  
  148.     /*
  149.      * main loop
  150.      */
  151.  
  152.     tzset();
  153.     now = get_time();
  154.     set_tzone();
  155.     create_list(&mainlist);
  156.     (void)readfile(&mainlist, DB_PUB_PATH,  TRUE);
  157.     (void)readfile(&mainlist, DB_PRIV_PATH, FALSE);
  158.     rebuild_repeat_chain(mainlist);
  159.     (void)recycle_all(mainlist, TRUE, 0);
  160. #ifdef ITIMER
  161.     itv.it_interval.tv_sec = 0;
  162.     itv.it_interval.tv_usec = 0;
  163.     itv.it_value.tv_usec = 0;
  164. #endif
  165.     for (;;) {
  166.         now = get_time();
  167.         set_tzone();
  168.         reason = find_next_trigger(mainlist, now, &entry, &twait);
  169.         if (reason && twait <= 0) {
  170.             alert(entry, reason);
  171.             entry->triggered = reason;
  172.         } else {
  173.             if (!reason)
  174.                 twait = HIBERNATE;
  175. #            ifdef DEBUG
  176.             printf("%s: sleeping for %d:%02d:%2d\n", progname,
  177.                     (int)twait/3600, (int)(twait/60)%60,
  178.                     (int)twait%60);
  179. #            endif
  180.             cutoff = now;
  181. #ifdef ITIMER
  182.             itv.it_value.tv_sec = twait;
  183.             setitimer(ITIMER_REAL, &itv, NULL);
  184. #if defined(SVR4) || defined(SOLARIS2)
  185.             sigpause(SIGALRM);
  186. #else
  187.             sigpause(0);
  188. #endif
  189. #else
  190.             sleep(twait);
  191. #endif
  192.         }
  193.         if (reread) {
  194. #            ifdef DEBUG
  195.                 printf("%s: re-reading database\n", progname);
  196. #            endif
  197.             destroy_list(&mainlist);
  198.             create_list(&mainlist);
  199.             (void)readfile(&mainlist, DB_PUB_PATH,  TRUE);
  200.             (void)readfile(&mainlist, DB_PRIV_PATH, FALSE);
  201.             rebuild_repeat_chain(mainlist);
  202.             reread = FALSE;
  203.         }
  204.         (void)recycle_all(mainlist, TRUE, 0);
  205. #ifndef RABBITS
  206.         if (pw) {
  207.             short pid = getpid();
  208.             setutent();
  209.             while (u = getutent())
  210.                 if (u->ut_type == USER_PROCESS &&
  211.                     u->ut_pid != pid &&
  212.                     !strncmp(pw->pw_name, u->ut_user, 8))
  213.                 break;
  214.             if (!u) {
  215. #                ifdef DEBUG
  216.                 printf("%s: logout, exiting\n", progname);
  217. #                endif
  218.                 exit(0);
  219.             }
  220.         }
  221. #endif
  222.     }
  223. }
  224.  
  225.  
  226. /*--------------------------------- warn/alarm action -----------------------*/
  227. /*
  228.  * An entry triggered, because its early-warn time, late-warn time, or alarm
  229.  * time was reached (reason is 1, 2, or 3, respectively). Do whatever was
  230.  * requested.
  231.  */
  232.  
  233. static void alert(entry, reason)
  234.     register struct entry    *entry;        /* entry that triggered */
  235.     int            reason;        /* why: 1=early,2=late,3=time*/
  236. {
  237.     switch(reason) {
  238.       case 1:
  239.         if (config.ewarn_window)
  240.             popup_window(reason, entry);
  241.         if (config.ewarn_mail)
  242.             send_mail(reason, entry);
  243.         if (config.ewarn_exec)
  244.             exec_program(entry, config.ewarn_prog, FALSE);
  245.         break;
  246.  
  247.       case 2:
  248.         if (config.lwarn_window)
  249.             popup_window(reason, entry);
  250.         if (config.lwarn_mail)
  251.             send_mail(reason, entry);
  252.         if (config.lwarn_exec)
  253.             exec_program(entry, config.lwarn_prog, FALSE);
  254.         break;
  255.  
  256.       case 3:
  257.         if (config.alarm_window)
  258.             popup_window(reason, entry);
  259.         if (config.alarm_mail)
  260.             send_mail(reason, entry);
  261.         if (config.alarm_exec)
  262.             exec_program(entry, config.alarm_prog, FALSE);
  263.         if (entry->script)
  264.             exec_program(entry, entry->script, TRUE);
  265.     }
  266. }
  267.  
  268.  
  269. /*
  270.  * pop up a window with the note text in it. This is done in a separate
  271.  * program that gets the text as standard input, to avoid having to drag
  272.  * in X stuff into the daemon. It is large enough as it is. The reason
  273.  * argument determines the color of the popup.
  274.  */
  275.  
  276. static popup_window(reason, entry)
  277.     int            reason;        /* why: 1=early,2=late,3=time*/
  278.     register struct entry    *entry;        /* entry that triggered */
  279. {
  280.     char            prog[1024];    /* path of notifier program */
  281.     char            cmd[2048];    /* command string */
  282.  
  283.     if (!find_file(prog, ALARM_FN, TRUE)) {
  284.         fprintf(stderr, "%s: %s: not found\n", progname, ALARM_FN);
  285.         return;
  286.     }
  287.     if (config.wintimeout > 59)
  288.         sprintf(cmd, "%s -%d -e%d -t\1%s\2 -i\1%s\2",
  289.                     prog, reason,
  290.                     (int)config.wintimeout/60,
  291.                     get_subject(reason, entry, FALSE),
  292.                     get_icontitle(reason, entry));
  293.     else
  294.         sprintf(cmd, "%s -%d -t\1%s\2 -i\1%s\2",
  295.                     prog, reason,
  296.                     get_subject(reason, entry, FALSE),
  297.                     get_icontitle(reason, entry));
  298.     exec_program(entry, cmd, FALSE);
  299. }
  300.  
  301.  
  302. /*
  303.  * send the entry's text to some user, as a mail message. The reason field
  304.  * is used for composing the Subject.
  305.  */
  306.  
  307. static send_mail(reason, entry)
  308.     int            reason;        /* why: 1=early,2=late,3=time*/
  309.     register struct entry    *entry;        /* entry that triggered */
  310. {
  311.     char            cmd[1500];    /* command string */
  312.     char            subject[82];    /* quoted subject */
  313.  
  314.     if (!config.mailer || !*config.mailer) {
  315.         fprintf(stderr, "%s: no mailer defined\n", progname);
  316.         return;
  317.     }
  318.     strcpy(subject+1, get_subject(reason, entry, TRUE));
  319.     subject[0] = 1;
  320.     strcat(subject, "\2");
  321.     sprintf(cmd, config.mailer, subject);
  322.     exec_program(entry, cmd, FALSE);
  323. }
  324.  
  325.  
  326.  
  327. /*
  328.  * execute a program, and pass the entry's message or note text as standard
  329.  * input. The program is executed as a child process, and reads the message
  330.  * from a pipe. The pipe is written to by a second child (unless the second
  331.  * fork fails, in which case the parent writes to the pipe) to avoid blocking
  332.  * the parent if the message is large and the child isn't really interested
  333.  * in the message. Blocking would freeze the daemon and could lose triggers.
  334.  * If the message is short (fits into one pipe write), don't use a 2nd child.
  335.  */
  336.  
  337. /*ARGSUSED*/
  338. static void reaper(sig)                /* lay dead children to rest */
  339. {
  340.     char            path[40];    /* script to delete */
  341. #if (defined BSD && !defined OSF)
  342.     union wait        dummy;
  343. #else
  344.     int            dummy;
  345. #endif
  346.     sprintf(path, "/tmp/pland%d", wait(&dummy));
  347. #    ifdef DEBUG
  348.         printf("%s: deleting script \"%s\"\n", progname, path);
  349. #    endif
  350.     (void)unlink(path);
  351.     signal(SIGCHLD, reaper);
  352. }
  353.  
  354.  
  355. static exec_program(entry, program, script)
  356.     register struct entry    *entry;        /* entry that triggered */
  357.     char            *program;    /* program or script to exec */
  358.     BOOL            script;        /* run program in a subshell */
  359. {
  360.     int            fd[2];        /* pipe to child process */
  361.     PID_T            pid;        /* child process id */
  362.     char            *p = program;    /* source command line */
  363.     char            *q = program;    /* target command line */
  364.     char            *argv[100];    /* argument vector */
  365.     int            argc = 0;    /* argument counter */
  366.     char            scriptname[40];    /* if script, temp file name */
  367.     int            quote;        /* argument quote flags */
  368.     char            *msg;        /* msg to print */
  369.     long            msgsize;    /* strlen(msg) */
  370.     char            progpath[1024];    /* path name of executable */
  371.  
  372.     if (!script) {                /* build arguments */
  373.         if (!p) return;
  374.         while (argc < 99) {
  375.             argv[argc++] = q;
  376.             quote = 0;
  377.             while (*p && (quote | *p) != ' ') {
  378.                 switch(*p) {
  379.                   case '\'':
  380.                      if (!(quote & 0x600))
  381.                         quote ^= 0x100;
  382.                      break;
  383.                   case '"':
  384.                      if (!(quote & 0x500))
  385.                         quote ^= 0x200;
  386.                      break;
  387.                   case 1:
  388.                      quote |= 0x400;
  389.                      break;
  390.                   case 2:
  391.                      quote &= ~0x400;
  392.                      break;
  393.                   case '\\':
  394.                     if (!quote && p[1])
  395.                         p++;
  396.                   default:
  397.                     *q++ = *p;
  398.                 }
  399.                 p++;
  400.             }
  401.             if (!*p++)
  402.                 break;
  403.             *q++ = 0;
  404.             while (*p == ' ')
  405.                 p++;
  406.         }
  407.         *q = 0;
  408.         argv[argc] = 0;
  409.  
  410.         if (!find_file(progpath, argv[0], TRUE)) {
  411.             FILE *err;
  412.             if (err = fopen("/dev/console", "w")) {
  413.                 fprintf(err, "%s: %s: not found\n",
  414.                             progname, argv[0]);
  415.                 fclose(err);
  416.             }
  417.             return;
  418.         }
  419. #ifdef DEBUG
  420.         {
  421.             int i;
  422.             printf("%d: EXECUTABLE = \"%s\"\n", progname,progpath);
  423.             for (i=0; i < argc; i++)
  424.                 printf("%d: ARGV[%d] = \"%s\"\n",
  425.                             progname, i, argv[i]);
  426.         }
  427. #endif
  428.     }
  429.     if (pipe(fd)) {                /* pipe from child 2 to ch 1 */
  430.         perror(progname);
  431.         return;
  432.     }
  433.     signal(SIGCHLD, reaper);
  434.     if ((pid = fork()) < 0) {
  435.         perror(progname);
  436.         return;
  437.     }
  438.     if (!pid) {                /* child 1, execs program */
  439.         close(0);
  440.         dup(fd[0]);
  441.         close(fd[0]);
  442.         close(fd[1]);
  443.         ORPHANIZE;
  444.         if (script) {
  445.             write_script(scriptname, program);
  446.             strcpy(progpath, scriptname);
  447.             argv[0] = scriptname;
  448.             argv[1] = 0;
  449.         }
  450.         execv(progpath, argv);
  451.         perror(argv[0]);
  452.         exit(0);
  453.     }
  454.     pid = 1;
  455.     msg = entry->message ? entry->message :
  456.           entry->note    ? entry->note    : "";
  457.     msgsize = strlen(msg);
  458.  
  459.     if (msgsize <= PIPE_MAX ||        /* child 2, feeds child 1 */
  460.         (pid = fork()) <= 0) {
  461.         close(fd[0]);
  462.         (void)write(fd[1], msg, msgsize);
  463.         close(fd[1]);
  464.         if (!pid)
  465.             exit(0);
  466.     } else {                /* parent, do nothing */
  467.         close(fd[0]);
  468.         close(fd[1]);
  469.     }
  470. }
  471.  
  472.  
  473.  
  474. /*
  475.  * write script to a file. The script name is generated from the process ID,
  476.  * so that reaper() knows which script to delete after this process terminates.
  477.  * The buffer for the script name is in the calling routine; it has to know
  478.  * because it is going to exec it. If something goes wrong, exit; we are in
  479.  * a child here. (Actually, this is what makes the whole thing look difficult:
  480.  * the script can't be written by the daemon, it must be written by the
  481.  * child that execs, because the script file name contains the child's PID,
  482.  * which isn't known before the fork. Putting the PID into the script file
  483.  * name makes it easy to delete the script file when the child dies, without
  484.  * keeping track of which child runs which script.)
  485.  * If the first line does not begin with "#!", "#!/bin/sh\n" is inserted.
  486.  */
  487.  
  488. static write_script(scriptname, program)
  489.     char            *scriptname;    /* script name buffer */
  490.     char            *program;    /* script text */
  491. {
  492.     int            len;        /* size of program */
  493.     int            fd;        /* script file */
  494.  
  495.     sprintf(scriptname, "/tmp/pland%d", getpid());
  496. #    ifdef DEBUG
  497.         printf("%s: generating script \"%s\"\n", progname, scriptname);
  498. #    endif
  499.     if ((fd = open(scriptname, O_WRONLY | O_CREAT | O_TRUNC, 0777)) < 0) {
  500.         int err = errno;
  501.         fprintf(stderr, "%s: can't create %s: ", progname, scriptname);
  502.         errno = err;
  503.         perror("");
  504.         exit(1);
  505.     }
  506.     len = strlen(program);
  507.     if (program[0] != '#' && program[1] != '!' &&
  508.         write(fd, "#!/bin/sh\n", 10) != 10 ||
  509.         write(fd, program, len) != len) {
  510.         int err = errno;
  511.         fprintf(stderr, "%s: can't write to %s: ",progname,scriptname);
  512.         errno = err;
  513.         perror("");
  514.         close(fd);
  515.         (void)unlink(scriptname);
  516.         exit(1);
  517.     }
  518.     (void)write(fd, "\n", 1);
  519.     close(fd);
  520. }
  521.  
  522.  
  523.  
  524. /*
  525.  * Return some descriptive message, to be used as a Subject or header text
  526.  * to tell the user what is happening. At most 79 characters are returned.
  527.  */
  528.  
  529. static char *get_subject(reason, entry, withnote)
  530.     int            reason;        /* why: 1=early,2=late,3=time*/
  531.     register struct entry    *entry;        /* entry that triggered */
  532.     BOOL            withnote;    /* append note string too */
  533. {
  534.     static char        msg[80];
  535.     time_t            time = (entry->time % 86400) / 60;
  536.     char            ampm = 0;
  537.  
  538.     if (config.ampm) {
  539.         ampm = time < 12*60 ? 'a' : 'p';
  540.         time %= 12*60;
  541.         if (time < 60)
  542.             time = 12*60;
  543.     }
  544.     sprintf(msg, reason==1 || reason==2 ? "Warning: alarm at %d:%02d%c"
  545.                         : "ALARM at %d:%02d%c",
  546.                                 time/60, time%60, ampm);
  547.     if (withnote && entry->note != 0) {
  548.         int len = strlen(msg);
  549.         sprintf(msg+len, " (%.50s", entry->note);
  550.         len = strlen(msg);
  551.         if (msg[len-1] == '\n')
  552.             len--;
  553.         msg[len] = ')';
  554.         msg[len+1] = 0;
  555.     }
  556.     return(msg);
  557. }
  558.  
  559.  
  560.  
  561. /*
  562.  * Return a short title string for the notifier icon title. It only
  563.  * contains the time of the trigger, and "early" or "late" flags.
  564.  */
  565.  
  566. static char *get_icontitle(reason, entry)
  567.     int            reason;        /* why: 1=early,2=late,3=time*/
  568.     register struct entry    *entry;        /* entry that triggered */
  569. {
  570.     static char        msg[20];
  571.     time_t            time = (entry->time % 86400) / 60;
  572.     char            ampm = 0;
  573.  
  574.     if (config.ampm) {
  575.         ampm = time < 12*60 ? 'a' : 'p';
  576.         time %= 12*60;
  577.         if (time < 60)
  578.             time = 12*60;
  579.     }
  580.     sprintf(msg, "%d:%02d%c", (int)(time/60), (int)(time%60), ampm);
  581.     if (reason == 1)
  582.         strcat(msg, " (E)");
  583.     if (reason == 2)
  584.         strcat(msg, " (L)");
  585.     return(msg);
  586. }
  587.  
  588.  
  589.  
  590. /*--------------------------------- misc ------------------------------------*/
  591. /*
  592.  * signal handler. SIGHUP re-reads the database by interrupting the sleep();
  593.  * all others terminate the program and delete the lockfile.
  594.  */
  595.  
  596. static void sighand(sig)
  597.     int            sig;        /* signal type */
  598. {
  599. #ifdef ITIMER
  600.     if (sig == SIGALRM)
  601.         signal(SIGALRM, sighand);
  602.     else
  603. #endif
  604.     if (sig == SIGHUP) {                /* re-read database */
  605.         signal(SIGHUP, sighand);
  606.         reread = TRUE;
  607.     } else {                    /* die */
  608.         (void)unlink(lockpath);
  609.         fprintf(stderr, "%s: killed with signal %d\n", progname, sig);
  610.         exit(0);
  611.     }
  612. }
  613.  
  614.  
  615. /*
  616.  * whenever something goes seriously wrong, this routine is called. It makes
  617.  * code easier to read. fatal() never returns. This may fail horribly if
  618.  * VARARGS is defined, but at least it's going to do *something*.
  619.  */
  620.  
  621. #ifndef VARARGS
  622. /*VARARGS*/
  623. void fatal(char *fmt, ...)
  624. {
  625.     va_list            parm;
  626.     int            err = errno;    /* fprintf may clear errno */
  627.  
  628.     va_start(parm, fmt);
  629.     fprintf(stderr, "%s: ", progname);
  630.     vfprintf(stderr, fmt, parm);
  631.     va_end(parm);
  632.     putc('\n', stderr);
  633.     exit(1);
  634. }
  635.  
  636. #else
  637. /*VARARGS*/
  638. fatal(fmt, a, b, c, d)
  639.     char    *fmt;
  640.     int    a, b, c, d;
  641. {
  642.     fprintf(stderr, fmt, a, b, c, d);
  643.     putc('\n', stderr);
  644.     exit(1);
  645. }
  646. #endif
  647.  
  648.  
  649. /*
  650.  * Ultrix doesn't have strdup, so we'll need to define one locally.
  651.  */
  652.  
  653. char *mystrdup(s)
  654.     register char *s;
  655. {
  656.     register char *p = NULL;
  657.  
  658.     if (s && (p = (char *)malloc(strlen(s)+1)))
  659.             strcpy(p, s);
  660.     return(p);
  661. }
  662.